[bits 32]
%include "lib/global.inc"
%include "lib/portio.inc"

;定义错误码结果，保证入栈时候的格式统一 
%define PRE_ERROR_CODE  nop
%define NO_ERROR_CODE   push 0
SECTION .data
    extern IntrSolveTable ;idt表的位置

SECTION .text
;-------------------------------------------------------------------------------
;name : InitPic
;function : 初始化中断控制器8259A
;input : 
;       void
;output :
;       void


;8259A的控制寄存器：
;四个初始化寄存器
;ICW1:  | 0 | 0 | 0 | 1 | LTIM | ADI | SNGL | IC4 | 
;       IC4 —— 是否使用IC4(x86系统IC4必须为1)
;       SNGL —— single， 为1表示单片， 为0表示级联
;       ADI —— 设置8085调用时间间隔， x86不需要设置
;       LTIME —— 中断检测方式， 为0表示边沿触发，为1表示电平触发
;ICW2: 设置起始中断向量号， 即8259A的接口IR0对应的起始中断，后续将依次排列, 要同时设置到主片和从片中
;ICW3: 仅仅在级联状态下使用，用来设置主片和从片用哪个IRQ接口互连。只用设置低3位标识主机中的从片接口
;ICW4: | 0 | 0 | 0 | SFNM | BUF | M/S | AEOI | uPM |
;       SFNM —— 标识是否处于特殊全嵌套模式
;       BUF —— 标识是否处于缓冲模式
;       M /S —— 标识是主片还是从片， 仅仅在缓冲模式有效
;       AEOI —— 是否自动结束中断
;       uPM —— 微处理器类型， 0——8080、8085， 1——x86

;四个操作寄存器
;OCW1:  屏蔽外部设表的中断信号， 某位为1则屏蔽该接口信号
;OCW2:  | R | SL | EOI | 0 | 0 | L2 | L1 | L0 |  
;设置中断结束模式和优先级模式
;低三位指定SL优先级别，高三位组合成各种功能
;       R —— Rotation, 是否按照循环方式设置中断优先级.即当某位的中断完成以后，它的优先级变为最低，优先执行其他中断
;       SL —— Specific Level, 是否指定优先等级。打开 SL以后通过第三位传入优先等级
;       EOI —— 中断命令结束位， 若其为1就会将ISR(当前处理中断)当前中断清理掉, 需要手动将EOI发给8259A
;OCW3:  | / | ESMM | SMM | 0 | 1 | P | PR | RIS | 
;       ESMM, ESS, 特殊屏蔽模式的允许位和启动位 
;       P 查询命令， 为1则可以通过寄存器读取IRS查看当前中断处理情况
;       RR，RIS允许和启动读取寄存器命令，当两者都为1时可以读取寄存器 

;ICW1和OCW2,OCW3通过0x20(M), 0xA0(S)写入
;ICW2~ICW4和OCW1通过0x21(M), 0xA1（S）写入
;四个ICW必须按照顺序写入，3个OCW部分任意顺续写入（它们的命令中的4~3标识位）
;当输入完ICW时，再输入偶数端口的将被认为是OCW
;-------------------------------------------------------------------------------
InitPic:
    push eax
    push edx
    ;主片的初始化
    ;0x20端口，主片控制端口
    ;输入ICW1：边沿出发，级联8259， 需要ICW4
    OUTB PIC_M_CTRL, 0x11
    ;0x21端口，主片数据端口
    ;输入ICW2：中断的起始向量号位0x20
    OUTB PIC_M_DATA, 0x20
    ;主片数据端口
    ;ICW3: IR2接从片
    OUTB PIC_M_DATA, 0x04
    ;主片数据端口
    ;ICW4: 8086模式，手动EOI
    OUTB PIC_M_DATA, 0x01
    
    ;从片的初始化
    ;0xA0端口， 从片控制端口
    ;输入ICW1: 边沿触发，级联8259， 需要ICW4
    OUTB PIC_S_CTRL, 0x11
    ;0x21端口，从片数据端口
    ;输入ICW2：中断的起始向量号位0x28
    OUTB PIC_S_DATA, 0x28
    ;从片数据端口
    ;ICW3: 从片接到主片的IR2上
    OUTB PIC_S_DATA, 0x02
    ;从片数据端口
    ;ICW4: 8086模式，手动EOI
    OUTB PIC_S_DATA, 0x01

    OUTB PIC_M_DATA, 0xf8
    OUTB PIC_S_DATA, 0xbf
    ; ; 屏蔽大部分中断，只留下时钟和从片，其他全部关闭
    ; OUTB PIC_M_DATA, 0xfe
    ; ; 打开和主片的连接通路，其他全部屏蔽
    ; OUTB PIC_S_DATA, 0xff

    pop edx
    pop eax
    ret
;-------------------------------------------------------------------------------
;name : IntrEntryTable
;function : save all the interrupt controller of program
;-------------------------------------------------------------------------------
%macro INTR_VECTOR 2
SECTION .text
IntrEntry%1:    ;每一个中断处理程序都要有一个标号，由此来标识调用它的中断号    
    %2        ;对齐栈中的错误码
    ;保存现场,压入寄存器的值
    push ds
    push es
    push fs
    push gs
    pushad
    
    ;与中断处理器通信，手工结束中断
    ;0x20是EOI信号 
    ;0xa0和0x20分别是主片和从片的端口
    mov al, 0x20
    out 0xa0, al
    out 0x20, al

    ;调用真正的中断处理程序
    push %1     ;压入中断号，方便调试
    call [IntrSolveTable + %1 * 4]  ;调用给定的程序的处理程序
    jmp intrExit

SECTION .data
    dd IntrEntry%1  ;存储中断的入口地址，形成IntrEntryTable数组

%endmacro

section .text
intrExit:
    add esp, 4
    popad
    pop gs
    pop fs
    pop es
    pop ds
    add esp, 4
    iretd



;-------------------------------------------------------------------------------
;以下生成中断向量处理函数入口表并且将其对应的错误指示出来
SECTION .data
global IntrEntryTable
IntrEntryTable:
;Part1 —— 系统中断

;0x00: Divide Error #DE
;src: DIV and IDIV instruction
INTR_VECTOR 0x00, NO_ERROR_CODE
;0x01: Debug    #DB
;src: TRAP condition code(TF) / Fault
INTR_VECTOR 0x01, NO_ERROR_CODE
;0x02: non-maskable external interrupt
;硬件级别严重错误
INTR_VECTOR 0x02, NO_ERROR_CODE
;0x03: Breakpoint   #BP
;int3 指令生成的断点
INTR_VECTOR 0x03, NO_ERROR_CODE
;0x04: Overflow     #OF
;溢出中断
INTR_VECTOR 0x04, NO_ERROR_CODE
;0x05:BOUND Range Exceeded  #BR
;数组越界
INTR_VECTOR 0x05, NO_ERROR_CODE
;0x06:Invalid Opcode    #UD
;无效操作码
INTR_VECTOR 0x06, NO_ERROR_CODE
;0x07:Device not available  #NM
;设备忙
INTR_VECTOR 0x07, NO_ERROR_CODE
;0x08:Double Fault  #DF
;????????????????
INTR_VECTOR 0x08, PRE_ERROR_CODE
;0x09 CoProcessor Segment Overrun   #MF
;???????????????? 
INTR_VECTOR 0x09, NO_ERROR_CODE
;0x0a: Invalid TSS #TS
INTR_VECTOR 0x0a, PRE_ERROR_CODE
;0x0b: Segment Not Present  #NP
INTR_VECTOR 0x0b, PRE_ERROR_CODE
;0x0c: Stack Segment Fault  #SS
INTR_VECTOR 0x0c, PRE_ERROR_CODE
;0x0d: General Protection
;Check in protection
INTR_VECTOR 0x0d, PRE_ERROR_CODE
;0x0e: Page fault
INTR_VECTOR 0x0e, PRE_ERROR_CODE
;0x0f: Reserved
INTR_VECTOR 0x0f, NO_ERROR_CODE
;0x10: Floating-Point Error(Math Fault) #MF
INTR_VECTOR 0x10, NO_ERROR_CODE
;0x11: Alignment Check  #AC
INTR_VECTOR 0x11, PRE_ERROR_CODE
;0x12: Machine Check #MC
INTR_VECTOR 0x12, NO_ERROR_CODE
;0x13: SIMD Floating-point Exception #XM
INTR_VECTOR 0x13, NO_ERROR_CODE
;0x14~0x1f  Reserved
INTR_VECTOR 0x14, NO_ERROR_CODE
INTR_VECTOR 0x15, NO_ERROR_CODE
INTR_VECTOR 0x16, NO_ERROR_CODE
INTR_VECTOR 0x17, NO_ERROR_CODE
INTR_VECTOR 0x18, NO_ERROR_CODE
INTR_VECTOR 0x19, NO_ERROR_CODE
INTR_VECTOR 0x1a, NO_ERROR_CODE
INTR_VECTOR 0x1b, NO_ERROR_CODE
INTR_VECTOR 0x1c, NO_ERROR_CODE
INTR_VECTOR 0x1d, NO_ERROR_CODE
INTR_VECTOR 0x1e, NO_ERROR_CODE
INTR_VECTOR 0x1f, NO_ERROR_CODE

;Part 2 ———— 可用中断
INTR_VECTOR 0x20, NO_ERROR_CODE	;时钟中断对应的入口
INTR_VECTOR 0x21, NO_ERROR_CODE	;键盘中断对应的入口
INTR_VECTOR 0x22, NO_ERROR_CODE	;级联用的
INTR_VECTOR 0x23, NO_ERROR_CODE	;串口2对应的入口
INTR_VECTOR 0x24, NO_ERROR_CODE	;串口1对应的入口
INTR_VECTOR 0x25, NO_ERROR_CODE	;并口2对应的入口
INTR_VECTOR 0x26, NO_ERROR_CODE	;软盘对应的入口
INTR_VECTOR 0x27, NO_ERROR_CODE	;并口1对应的入口
INTR_VECTOR 0x28, NO_ERROR_CODE	;实时时钟对应的入口
INTR_VECTOR 0x29, NO_ERROR_CODE	;重定向
INTR_VECTOR 0x2a, NO_ERROR_CODE	;保留
INTR_VECTOR 0x2b, NO_ERROR_CODE	;保留
INTR_VECTOR 0x2c, NO_ERROR_CODE	;ps/2鼠标
INTR_VECTOR 0x2d, NO_ERROR_CODE	;fpu浮点单元异常
INTR_VECTOR 0x2e, NO_ERROR_CODE	;硬盘
INTR_VECTOR 0x2f, NO_ERROR_CODE	;保留
; SECTION .data 
    ; times 0x50 dd 0h
;剩下的0x30到0x79中断暂时用不到，就先不在这里定义了，只要不用，就不会出bug

;-------------------------------------------------------------------------------
;初始化IDT中断描述符表
;中断描述符结构如下:
;低32位：   | DESC_SELECTOR 31~16 |                     OFFSET15~0                       |
;高32位：   |     OFFSET31~16     | P | DPL | S | TYPE_D | 1 | 1 | 0 | 0 | 0 | 未使用4~0 |

;加载idt的指令为lidt，加载的结构为：
;   IdtTable | sizeof(IdtTable)-1
;      32位  |      32位

SECTION .data 
global IdtTable
IdtTable:
    times IDT_SIMPLE_CNT + 1 dq 0h

lidt_param dq 0h

SECTION .text
extern PutString_S
global InitIdt
InitIdt:
    push ebx
    push esi
    push edi
    mov eax, IDTInitStartPrompt
    push eax
    call PutString_S
    add esp, 4
    call InitPic
    ;完成普通中断描述符表的定义
    push ecx
    xor ecx, ecx
.LOOP:
    mov eax, [IntrEntryTable + ecx * 4]
    mov edx, eax
    and eax, 0x0000FFFF
    or eax, SELECTOR_CODE << 16
    mov [IdtTable + ecx * 8h], eax
    and edx, 0xFFFF0000
    or edx, DESC_P_1 | DESC_DPL_0 | (!DESC_S_USR) | IDT_DESC_TYPE_D_32 | 110_000_00000b
    mov [IdtTable + ecx * 8h + 4], edx 
    inc ecx
    cmp ecx, IDT_SIMPLE_CNT
    jne .LOOP
    ;最后补充一个系统中断的调用即可
    mov eax, syscall_handler
    mov edx, eax
    and eax, 0x0000FFFF
    or eax, SELECTOR_CODE << 16
    mov [IdtTable + IDT_SYSCALL_HANDLER * 8h], eax
    and edx, 0xFFFF0000
    or edx, DESC_P_1 | DESC_DPL_3 | (!DESC_S_USR) | IDT_DESC_TYPE_D_32 | 110_000_00000b
    mov [IdtTable + IDT_SYSCALL_HANDLER * 8h + 4], edx 
    
    mov dword [lidt_param + 2], IdtTable
    mov word [lidt_param], IDT_SIMPLE_CNT * 8h + 8h
    lidt [lidt_param]
    pop ecx
    mov eax, IDTInitEndPrompt
    push eax
    call PutString_S
    add esp, 4
    pop edi
    pop esi
    pop ebx
    ret

IDTInitStartPrompt db 'IDT init start', 0x0a, 0x00
IDTInitEndPrompt db 'IDT init end', 0x0a, 0x00

SECTION .data
global syscall_table
syscall_table:
    times 0xFF dd 0h

SECTION .text
syscall_handler:
    ;错误码占位
    push 0

    ;保存寄存器现场
    push ds
    push es
    push fs
    push gs
    ; pushad = push eax, ecx, edx, ebx, esp, ebp, esi, edi
    pushad

    ;传入中断调用号
    push 0x80

    ;传入参数
    push edx    ;第三个参数
    push ecx    ;第二个参数
    push ebx    ;第一个参数

    ;调用表中正确的处理函数
    call [syscall_table + eax * 4]
    add esp, 12 ;释放传参

    mov [esp + 8 * 4], eax
    jmp intrExit